/*
 * sound\soc\sunxi\sunxi_sndspdif.c
 * (C) Copyright 2010-2016
 * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
 * wolfgang huang <huangjinhui@allwinnertech.com>
 *
 * some simple description for this code
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 */

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/io.h>
#include <linux/of.h>
#include "sunxi_spdif.h"
#include "codec-utils.h"

static int sunxi_sndspdif_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int ret, clk_div;
	unsigned int freq;

	switch(params_rate(params)) {
	case	24000:
	case	32000:
	case	48000:
	case	96000:
	case	192000:
		freq = 24576000;
		break;
	case	22050:
	case	44100:
	case	176400:
		freq = 22579200;
		break;
	default:
		pr_debug("[sunxi spdif]Invalid rate\n");
		return -EINVAL;
	}

	ret = snd_soc_dai_set_sysclk(cpu_dai, 0 , freq, 0);
	if (ret < 0)
		return ret;

	clk_div = freq/params_rate(params);
	clk_div = clk_div>>7;

	ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, clk_div);
	if(ret < 0)
		return ret;

	return 0;
}

static struct snd_soc_ops sunxi_sndspdif_ops = {
	.hw_params 	= sunxi_sndspdif_hw_params,
};

static struct snd_soc_dai_link sunxi_sndspdif_dai_link = {
	.name 			= "SPDIF",
	.stream_name 	= "SUNXI-SPDIF",
	.cpu_dai_name 	= "sunxi-spdif",
	.platform_name 	= "sunxi-spdif",
	.codec_dai_name = "spdif-hifi",
	.codec_name	= "spdif-utils",
	.ops 		= &sunxi_sndspdif_ops,
};

static struct snd_soc_card snd_soc_sunxi_sndspdif = {
	.name 		= "sndspdif",
	.owner 		= THIS_MODULE,
	.dai_link 	= &sunxi_sndspdif_dai_link,
	.num_links 	= 1,
};

static int sunxi_sndspdif_dev_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct snd_soc_card *card = &snd_soc_sunxi_sndspdif;
	struct device_node *np = pdev->dev.of_node;
	struct platform_device *sunxi_snd_spdif_utils_device;

	card->dev = &pdev->dev;
	sunxi_sndspdif_dai_link.cpu_dai_name = NULL;
	sunxi_sndspdif_dai_link.cpu_of_node = of_parse_phandle(np,
				"sunxi,spdif-controller", 0);
	if (!sunxi_sndspdif_dai_link.cpu_of_node) {
		dev_err(&pdev->dev,
			"Property 'sunxi,spdif-controller' missing or invalid\n");
			ret = -EINVAL;
	}
	sunxi_sndspdif_dai_link.platform_name = NULL;
	sunxi_sndspdif_dai_link.platform_of_node = sunxi_sndspdif_dai_link.cpu_of_node;

	sunxi_snd_spdif_utils_device = platform_device_alloc("spdif-utils", -1);
	if(sunxi_snd_spdif_utils_device == NULL) {
		dev_err(&pdev->dev, "spdif utils alloc failed\n");
		return -ENOMEM;
	}

	ret = platform_device_add(sunxi_snd_spdif_utils_device);
	if(ret) {
		dev_err(&pdev->dev, "spdif utils add failed\n");
		ret = -EBUSY;
		goto err_utils_put;
	}
	
	ret = snd_soc_register_card(card);
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret);
		ret = -EBUSY;
		goto err_utils_del;
	}
	return ret;

err_utils_del:
	platform_device_del(sunxi_snd_spdif_utils_device);
err_utils_put:
	platform_device_put(sunxi_snd_spdif_utils_device);
	return ret;
}

static int sunxi_sndspdif_dev_remove(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	snd_soc_unregister_card(card);
	return 0;
}

static const struct of_device_id sunxi_spdif_of_match[] = {
	{ .compatible = "allwinner,sunxi-spdif-machine", },
	{},
};

static struct platform_driver sunxi_spdif_driver = {
	.driver = {
		.name = "sndspdif",
		.owner = THIS_MODULE,
		.of_match_table = sunxi_spdif_of_match,
		.pm = &snd_soc_pm_ops,
	},
	.probe = sunxi_sndspdif_dev_probe,
	.remove = sunxi_sndspdif_dev_remove,
};

module_platform_driver(sunxi_spdif_driver);

MODULE_AUTHOR("wolfgang huang <huangjinhui@allwinnertech.com>");
MODULE_DESCRIPTION("SUNXI SPDIF ASoC audio driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sndspdif");
